package socketmvc.server.ws;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.TioConfig;
import org.tio.core.intf.Packet;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.common.WsResponse;
import org.tio.websocket.server.handler.IWsMsgHandler;
import socketmvc.core.packets.SocketClosePacket;
import socketmvc.core.adapter.SocketComponentAdapter;
import socketmvc.server.context.IServerContext;
import socketmvc.server.context.SocketChannel;
import socketmvc.server.context.ThreadSocketChannelHandler;
import socketmvc.core.exception.ResponseException;
import socketmvc.core.filter.AntPathFilterChain;
import socketmvc.core.packets.IOPacket;
import socketmvc.core.packets.ErrorPacket;
import socketmvc.core.packets.WsHandshakePacket;
import socketmvc.core.packets.basic.IPacket;
import socketmvc.core.util.PacketHandler;
import java.nio.ByteBuffer;

public class WsMsgHandler implements IWsMsgHandler {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    private final IServerContext serverContext;

    private final PacketHandler packetHandler;

    private final SocketComponentAdapter componentAdapter;

    public WsMsgHandler(IServerContext serverContext) {
        this.serverContext = serverContext;
        this.componentAdapter = new SocketComponentAdapter(
                serverContext.getMvcConfig().getProcessorMethodHandler(),
                serverContext.getMvcConfig().getProtocolCodecHandler(),
                serverContext.getMvcConfig().getFilterHandler());
        this.packetHandler = new PacketHandler(this.componentAdapter);
    }

    @Override
    public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) {
        WsHandshakePacket imWsHandshakePacket = new WsHandshakePacket(httpRequest, httpResponse, channelContext);
        SocketChannel context = new SocketChannel(channelContext, httpRequest.isSslEncrypted() ? "wss" : "ws");
        context.setPacket(imWsHandshakePacket);
        context.setServerContext(serverContext);
        ThreadSocketChannelHandler.set(context);
        // 构造过滤器器链条，并开始执行链条内的任务
        AntPathFilterChain filterChain = new AntPathFilterChain(componentAdapter.getFilters(context,imWsHandshakePacket));
        try {
            filterChain.doFilter(context, null);
        } catch (Exception e) {
            // 有异常捕获，代表拒绝握手
            printErr("An exception occurred in the websocket handshake ===>", e);
            return null;
        } finally {
            ThreadSocketChannelHandler.remove();
        }
        // 直接允许握手
        return httpResponse;
    }

    @Override
    public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) {
        WsHandshakePacket imWsHandshakePacket = new WsHandshakePacket(httpRequest, httpResponse, channelContext);
        imWsHandshakePacket.setHandshakeFlag((byte) 1);
        SocketChannel context = new SocketChannel(channelContext, httpRequest.isSslEncrypted() ? "wss" : "ws");
        context.setPacket(imWsHandshakePacket);
        context.setServerContext(serverContext);
        ThreadSocketChannelHandler.set(context);
        // 构造过滤器器链条，并开始执行链条内的任务
        AntPathFilterChain filterChain = new AntPathFilterChain(componentAdapter.getFilters(context,imWsHandshakePacket));
        try {
            filterChain.doFilter(context, null);
        } catch (Exception e) {
            printErr("An exception occurred in the websocket after handshake ===>", e);
            context.send(new ErrorPacket(e));
        } finally {
            ThreadSocketChannelHandler.remove();
        }
    }

    /**
     * 字节消息（binaryType = arraybuffer）过来后会走这个方法
     */
    @Override
    public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) {
//       WsSessionContext wsSessionContext = (WsSessionContext) channelContext.get();
//       HttpRequest httpRequest = wsSessionContext.getHandshakeRequest();//获取websocket握手包
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        IPacket imPacket = packetHandler.toPacket(buffer);

        SocketChannel context = new SocketChannel(channelContext, wsRequest.isSslEncrypted() ? "wss" : "ws");
        context.setPacket(imPacket);
        context.setServerContext(serverContext);
        ThreadSocketChannelHandler.set(context);
        // 构造过滤器器链条，并开始执行链条内的任务
        AntPathFilterChain filterChain = new AntPathFilterChain(componentAdapter.getFilters(context,imPacket));
        try {
            filterChain.doFilter(context, null);
        }catch (ResponseException re){
            printErr("An exception occurred in the filter chain ===> cmd:"+imPacket.getCommand(), re);
            return WsResponse.fromBytes(packetHandler.toByteBuffer(
                    new ErrorPacket(re.getResponseMsg(),imPacket.getResponseKey())).array());
        } catch (Exception e) {
            printErr("An exception occurred in the filter chain ===> cmd:"+imPacket.getCommand(), e);
            return WsResponse.fromBytes(packetHandler.toByteBuffer(new ErrorPacket(e,imPacket)).array());
        } finally {
            ThreadSocketChannelHandler.remove();
        }
        return null;
    }

    /**
     * 当客户端发close flag时或者服务端主动调用close方法{@link SocketChannel#close()}
     * 会进入该回调函数
     */
    @Override
    public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) {
        ChannelContext.CloseCode closeCode = channelContext.getCloseCode();
        ChannelContext.CloseMeta closeMeta = channelContext.closeMeta;
        SocketChannel context = new SocketChannel(channelContext, wsRequest.isSslEncrypted() ? "wss" : "ws");
        SocketClosePacket packet = new SocketClosePacket();
        packet.putProperty("clientIp",channelContext.getClientNode().getIp());
        packet.putProperty("clientPort",channelContext.getClientNode().getPort());
        if (closeCode != null){
            packet.putProperty("code",closeCode.getValue());
        }
        if (closeMeta != null){
            packet.putProperty("reason",closeMeta.getRemark());
        }
        context.setPacket(packet);
        context.setServerContext(serverContext);
        ThreadSocketChannelHandler.set(context);

        // 构造过滤器器链条，并开始执行链条内的任务
        AntPathFilterChain filterChain = new AntPathFilterChain(componentAdapter.getFilters(context,packet));
        try {
            filterChain.doFilter(context, null);
        }catch (Exception e) {
            printErr("An exception occurred in the filter chain ===>", e);
            packet.getTextBodyAsObj().setBody(e.getLocalizedMessage());
        } finally {
            if (ChannelContext.CloseCode.USER_CODE_10 == closeCode){
                //服务端主动调用close {@link SocketChannel#close()}
                context.send(packet);
            }
            ThreadSocketChannelHandler.remove();
            context.close();
        }
        return null;
    }

    /*
     * 字符消息（binaryType = blob）过来后会走这个方法
     */
    @Override
    public Object onText(WsRequest wsRequest, String text, ChannelContext channelContext) {
        SocketChannel context = new SocketChannel(channelContext, wsRequest.isSslEncrypted() ? "wss" : "ws");
        SocketClosePacket packet = new SocketClosePacket(-99,"暂不支持text类型消息");
        packet.putProperty("clientIp",channelContext.getClientNode().getIp());
        packet.putProperty("clientPort",channelContext.getClientNode().getPort());
        context.send(packet);
        context.close();
        return null;
    }

    @Override
    public WsResponse encodeSubProtocol(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
        IOPacket ioPacket = (IOPacket) packet;
        byte[] array = null;
        try {
            array = packetHandler.toByteBuffer(ioPacket.getPacket()).array();
        } catch (Exception e) {
            printErr("An exception occurred in the encodeSubProtocol function ===>",e);
        }
        return WsResponse.fromBytes(array);
    }


    /**
     * 打印异常
     *
     * @param msg 异常信息
     * @param e   ex
     */
    private void printErr(String msg, Exception e) {
        if (logger.isErrorEnabled()) {
            logger.error(msg, e);
        } else {
            System.err.println(msg);
            e.printStackTrace(System.err);
        }
    }

}
